﻿using System;

namespace ICommonLibrary.Date {
    
    public class DateUtil {

        public static readonly int[] NormalYearDays = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
        public static readonly int[] LeapYearDays = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

        public static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        public static DateTime ConvertMillisSinceEpochToDateTime(long milliseconds) {
            return Epoch.Add(TimeSpan.FromMilliseconds(milliseconds));
        }

        public static long ConvertDateTimeToMillisSinceEpoch(DateTime dateTime) {
            return dateTime.Ticks/10000 - Epoch.Ticks/10000;
        }

        public static long GetTimeDiffInMillis(ref DateTime d1, ref DateTime d2) {
            return d2.Ticks / 10000 - d1.Ticks / 10000;
        }

        public static double GetElapsedTimeInHours(DateTime since) {
            return (DateTime.Now - since).TotalHours;
        }

        public static double GetElapsedTimeInMillis(DateTime since) {
            return (DateTime.Now - since).TotalMilliseconds;
        }

        public static double GetElapsedTimeInHours(long sinceInTicks) {
            if (sinceInTicks < DateTime.MinValue.Ticks) {
                return Double.MaxValue;
            }
            return GetElapsedTimeInHours(new DateTime(sinceInTicks));
        }

        public static DateTime GetStartOfLastWeek() {
            DateTime someTimeLastWeek = DateTime.Now.AddDays(-7);
            int daysToSubtract = 
                someTimeLastWeek.DayOfWeek - 
                DayOfWeek.Monday;
            
            DateTime startDate = someTimeLastWeek.AddDays(-daysToSubtract);
            return new DateTime(startDate.Year, startDate.Month, startDate.Day, 0, 0, 0, 0);
        }

        public static DateTime GetEndOfLastWeek() {
            DateTime dt = GetStartOfLastWeek().AddDays(6);
            return new DateTime(dt.Year, dt.Month, dt.Day, 23, 59, 59, 999);
        }

        public static DateTime GetStartOfCurrentWeek() {
            DateTime now = DateTime.Now;
            int daysToSubtract =
                now.DayOfWeek -
                DayOfWeek.Monday;

            DateTime startDate = now.AddDays(-daysToSubtract);
            return new DateTime(startDate.Year, startDate.Month, startDate.Day, 0, 0, 0, 0);
        }

        public static DateTime GetEndOfCurrentWeek() {
            DateTime dt = GetStartOfCurrentWeek().AddDays(6);
            return new DateTime(dt.Year, dt.Month, dt.Day, 23, 59, 59, 999);
        }

        public static int GetDaysOfMonth(int y, int m) {
            bool leap = IsLeapYear(y);
            return leap ? LeapYearDays[m] : NormalYearDays[m];
        }

        public static int GetDaysOfMonth(YearMonth ym) {
            return GetDaysOfMonth(ym.Year, ym.Month);
        }

        public static DateTime AddDays(DateTime date, int days) {
            return date.AddDays(days);
        }

        public static bool IsBeforeOrSame(DateTime date, DateTime baseDate) {
            if (DateTime.Compare(date, baseDate) < 0 || DateTime.Compare(date, baseDate) == 0)
                return true;
            return false;
        }

        public static bool IsLeapYear(int year) {
            return ((year % 100) != 0) && (year % 4) == 0 || (year % 400) == 0;
        }

        public static string FormatUtc(DateTime dateTime) {
            return dateTime.ToUniversalTime().ToString("u");
        }

        public static DateTime ParseUtc(string dateTimeStr, DateTime def) {
            if (String.IsNullOrEmpty(dateTimeStr)) {
                return def;
            }
            DateTime dateTime;
            if (DateTime.TryParse(dateTimeStr, out dateTime)) {
                return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
            }
            return def;
        }

        public static int DayDiff(MutableDate startDate, MutableDate endDate) {
    		
            if (startDate.Equals(endDate)) {
                return 0;
            }
    		
            int y1 = startDate.Year;
            int m1 = startDate.Month;
            int d1 = startDate.Date;
            bool y1Leap = IsLeapYear(y1);

            int y2 = endDate.Year;
            int m2 = endDate.Month;
            int d2 = endDate.Date;
    		
            int days = 0;
    		
            if (startDate.Before(endDate)) {
                int monthsDiff = (y2 * 12 + m2) - (y1 * 12 + m1);
                while (monthsDiff > 0) {
                    int remaining = (y1Leap ? LeapYearDays[m1] : NormalYearDays[m1]) - d1;
                    days += remaining;
                    if (m1 == 11) {
                        m1 = 0;
                        y1++;
                        y1Leap = IsLeapYear(y1);
                    }
                    else {
                        m1++;
                    }
                    d1 = 0;
                    monthsDiff--;
                }
                days += d2 - d1;
                return days;
            }
    		
            // startDate is after endDate
            int monthsDiff2 = (y1 * 12 + m1) - (y2 * 12 + m2);
            while (monthsDiff2 > 0) {
                days -= d1;
                if (m1 == 0) {
                    m1 = 11;
                    y1--;
                    y1Leap = IsLeapYear(y1);
                }
                else {
                    m1--;
                }
                d1 = y1Leap ? LeapYearDays[m1] : NormalYearDays[m1];
                monthsDiff2--;
            }
            days -= d1 - d2;
            return days;
        }

        public static int MonthDiff(MutableDate startDate, MutableDate endDate) {
            return (endDate.Year * 12 + endDate.Month) -
                   (startDate.Year * 12 + startDate.Month);
        }

        public static int YearDiff(MutableDate startDate, MutableDate endDate) {
            return endDate.Year - startDate.Year;
        }

        public static MutableDate GetNextDate(MutableDate date, Frequency freq) {
            if (freq == Frequency.Monthly) {
                return date.Clone().AddMonths(1);
            }
            if (freq == Frequency.Fortnightly) {
                return date.Clone().AddDays(14);
            }
            if (freq == Frequency.Weekly) {
                return date.Clone().AddDays(7);
            }
            if (freq == Frequency.Yearly) {
                return date.Clone().AddYears(1);
            }
            throw new ArgumentException("Unsupported frequency:" + freq);
        }
    }
}
